package com.codeanalysissys.backend.golang;

import com.codeanalysissys.backend.javascript.parser.JavaScriptLexer;
import com.codeanalysissys.backend.javascript.parser.JavaScriptParser;
import ghaffarian.graphs.*;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.*;

import com.codeanalysissys.backend.golang.parser.GoLexer;
import com.codeanalysissys.backend.golang.parser.GoParser;
import com.codeanalysissys.backend.graphs.ast.ASNode;
import com.codeanalysissys.backend.golang.parser.GoParserBaseVisitor;
import com.codeanalysissys.backend.graphs.ast.AbstractSyntaxTree;

import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.ParseTree;
import com.codeanalysissys.backend.golang.parser.GoParserBaseVisitor;
import ghaffarian.nanologger.Logger;
import org.antlr.v4.runtime.tree.TerminalNode;

/**
 * Abstract Syntax Tree (AST) builder for Golang programs.
 * A Java parser generated via ANTLRv4 is used for this purpose.
 * This implementation is based on ANTLRv4's Visitor pattern.
 *
 * @author Huang WeiQuan
 */

public class GoASTBuilder {
    public static AbstractSyntaxTree build(String goFile) throws IOException {
        return build(new File(goFile));
    }

    public static AbstractSyntaxTree build(InputStream inFile) throws IOException {
        ANTLRInputStream input = new ANTLRInputStream(inFile);
        GoLexer lexer = new GoLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        GoParser parser = new GoParser(tokens);
        ParseTree tree = parser.sourceFile();
        return build(tree, null, null);
    }

    public static AbstractSyntaxTree build(File goFile) throws IOException {
        if (!goFile.getName().endsWith(".go"))
            throw new IOException("Not a Golang File!");
        CharStream input = CharStreams.fromPath(Paths.get(goFile.getAbsolutePath()));            //将源代码文件输入流转换为Antlr的输入流
        GoLexer lexer = new GoLexer(input);                                  //Antlr生成的词法分析器
        CommonTokenStream tokens = new CommonTokenStream(lexer);                 //将词法分析结果转换为token流
        GoParser parser = new GoParser(tokens);                              //Antlr生成的语法分析器
        ParseTree tree = parser.sourceFile();                               //Java语法规则中的根规则-解析器已生成
        return build(goFile.getPath(), tree, null, null);
    }

    public static AbstractSyntaxTree build(String filename, InputStream inputStream) throws IOException {
        InputStream inFile = inputStream;
        ANTLRInputStream input = new ANTLRInputStream(inFile);
        GoLexer lexer = new GoLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        GoParser parser = new GoParser(tokens);
        ParseTree tree = parser.sourceFile();                               //Java语法规则中的根规则-解析器已生成
        return build(filename, tree, null, null);
    }

    public static AbstractSyntaxTree build(ParseTree tree, String propKey, Map<ParserRuleContext, Object> ctxProps) {
        GoASTBuilder.AbstractSyntaxVisitor visitor = new GoASTBuilder.AbstractSyntaxVisitor(propKey, ctxProps);         //采用Antlr的visitor遍历
        return visitor.build(tree);
    }

    public static AbstractSyntaxTree build(String filePath, ParseTree tree,
                                           String propKey, Map<ParserRuleContext, Object> ctxProps) {
        GoASTBuilder.AbstractSyntaxVisitor visitor = new GoASTBuilder.AbstractSyntaxVisitor(filePath, propKey, ctxProps);         //采用Antlr的visitor遍历
        Logger.debug("Visitor building AST of: " + filePath);
        return visitor.build(tree);
    }

    private static class AbstractSyntaxVisitor extends GoParserBaseVisitor<String> {
        private String propKey;
        private String typeModifier;
        private String memberModifier;
        private Deque<ASNode> parentStack;
        private final AbstractSyntaxTree AST;
        private Map<String, String> vars, fields, methods;
        private int varsCounter, fieldsCounter, methodsCounter;
        private Map<ParserRuleContext, Object> contexutalProperties;

        public AbstractSyntaxVisitor(String filePath, String propKey, Map<ParserRuleContext, Object> ctxProps) {
            parentStack = new ArrayDeque<>();
            AST = new AbstractSyntaxTree(filePath);
            this.propKey = propKey;
            contexutalProperties = ctxProps;
            vars = new LinkedHashMap<>();
            fields = new LinkedHashMap<>();
            methods = new LinkedHashMap<>();
            varsCounter = 0;
            fieldsCounter = 0;
            methodsCounter = 0;
        }

        public AbstractSyntaxVisitor(String propKey, Map<ParserRuleContext, Object> ctxProps) {
            parentStack = new ArrayDeque<>();
            AST = new AbstractSyntaxTree();
            this.propKey = propKey;
            contexutalProperties = ctxProps;
            vars = new LinkedHashMap<>();
            fields = new LinkedHashMap<>();
            methods = new LinkedHashMap<>();
            varsCounter = 0;
            fieldsCounter = 0;
            methodsCounter = 0;
        }

        public AbstractSyntaxTree build(ParseTree tree) {
            GoParser.SourceFileContext rootCntx = (GoParser.SourceFileContext) tree;
            AST.root.setCode(AST.filePath != null ? new File(AST.filePath).getName() : "root");
            parentStack.push(AST.root);
            if (rootCntx.packageClause() != null)
                visit(rootCntx.packageClause());
            //
            if (rootCntx.importDecl() != null && rootCntx.importDecl().size() > 0) {
                ASNode imports = new ASNode(ASNode.Type.IMPORTS);
                imports.setLineOfCode(rootCntx.importDecl(0).getStart().getLine());
                Logger.debug("Adding imports");
                AST.addVertex(imports);
                AST.addEdge(AST.root, imports);
                parentStack.push(imports);
                for (GoParser.ImportDeclContext importCtx : rootCntx.importDecl())
                    visit(importCtx);
                parentStack.pop();
            }
            //
            if (rootCntx.declaration() != null && rootCntx.declaration().size() > 0) {
                for (GoParser.DeclarationContext decCtx : rootCntx.declaration())
                    visit(decCtx);
            }
            //
            if (rootCntx.functionDecl() != null && rootCntx.functionDecl().size() > 0) {
                for (GoParser.FunctionDeclContext funcCtx : rootCntx.functionDecl())
                    visit(funcCtx);
            }
            //
            if (rootCntx.methodDecl() != null && rootCntx.methodDecl().size() > 0) {
                for (GoParser.MethodDeclContext methodCtx : rootCntx.methodDecl())
                    visit(methodCtx);
            }

            parentStack.pop();
            vars.clear();
            fields.clear();
            methods.clear();
            return AST;
        }

        //=====================================================================//
        //                           DECLARATIONS                              //
        //=====================================================================//

        @Override
        public String visitPackageClause(GoParser.PackageClauseContext ctx) {
            ASNode node = new ASNode(ASNode.Type.PACKAGE);
            if (ctx.qualifiedName() != null) {
                node.setCode(ctx.qualifiedName().getText());
                node.setLineOfCode(ctx.getStart().getLine());
                Logger.debug("Adding package");
                AST.addVertex(node);
                AST.addEdge(parentStack.peek(), node);
                return "";
            } else
                return "";
        }

        @Override
        public String visitImportDecl(GoParser.ImportDeclContext ctx) {
            int size = 0;
            String qualifiedName = "";
            if (ctx.importSpec(size).IDENTIFIER() != null)
                qualifiedName = ctx.importSpec(size).IDENTIFIER().getText();
            if (ctx.importSpec(size).importPath() != null)
                qualifiedName = qualifiedName + " " + ctx.importSpec(size).importPath().getText();
            size++;
            while (size < ctx.importSpec().size()) {
                if (ctx.importSpec(size).IDENTIFIER() != null)
                    qualifiedName = qualifiedName + " " + ctx.importSpec(size).IDENTIFIER().getText();
                if (ctx.importSpec(size).importPath() != null)
                    qualifiedName = qualifiedName + " " + ctx.importSpec(size).importPath().getText();
                size++;
            }
            ASNode node = new ASNode(ASNode.Type.IMPORT);
            node.setCode(qualifiedName);
            node.setLineOfCode(ctx.getStart().getLine());
            Logger.debug("Adding import " + qualifiedName);
            AST.addVertex(node);
            AST.addEdge(parentStack.peek(), node);
            return "";
        }

        @Override
        public String visitFunctionDecl(GoParser.FunctionDeclContext ctx) {
            ASNode modifierNode = new ASNode(ASNode.Type.MODIFIER);
            modifierNode.setCode(memberModifier);
            modifierNode.setLineOfCode(ctx.getStart().getLine());
            Logger.debug("Adding Func modifier");
            AST.addVertex(modifierNode);
            AST.addEdge(parentStack.peek(), modifierNode);
            //
            ASNode retNode = new ASNode(ASNode.Type.RETURN);
            if (ctx.signature().result() != null)
                retNode.setCode(ctx.signature().result().getText());
            else
                retNode.setCode("Void");
            retNode.setLineOfCode(ctx.getStart().getLine());
            Logger.debug("Adding Func type");
            AST.addVertex(retNode);
            AST.addEdge(parentStack.peek(), retNode);
            //
            ++methodsCounter;
            ASNode nameNode = new ASNode(ASNode.Type.NAME);
            String methodName = ctx.IDENTIFIER().getText();
            String normalized = "$FUNC_" + methodsCounter;
            methods.put(methodName, normalized);
            nameNode.setCode(methodName);
            nameNode.setNormalizedCode(normalized);
            nameNode.setLineOfCode(ctx.getStart().getLine());
            Logger.debug("Adding Func name");
            AST.addVertex(nameNode);
            AST.addEdge(parentStack.peek(), nameNode);
            //
            if (ctx.signature().parameters().parameterDecl() != null) {
                ASNode paramsNode = new ASNode(ASNode.Type.PARAMS);
                if (ctx.formalParameters() != null)
                    paramsNode.setLineOfCode(ctx.formalParameters().getStart().getLine());
                else
                    paramsNode.setLineOfCode(0);
                Logger.debug("Adding method params node");
                AST.addVertex(paramsNode);
                AST.addEdge(parentStack.peek(), paramsNode);
                parentStack.push(paramsNode);
                for (GoParser.ParameterDeclContext paramctx :
                        ctx.signature().parameters().parameterDecl()) {
                    ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                    varNode.setLineOfCode(paramctx.getStart().getLine());
                    AST.addVertex(varNode);
                    AST.addEdge(parentStack.peek(), varNode);
                    //
                    ASNode type = new ASNode(ASNode.Type.TYPE);
                    type.setCode(paramctx.identifierList().getText());
                    type.setLineOfCode(paramctx.identifierList().getStart().getLine());
                    AST.addVertex(type);
                    AST.addEdge(varNode, type);
                    //
                    ++varsCounter;
                    ASNode name = new ASNode(ASNode.Type.NAME);
                    normalized = "$VARL_" + varsCounter;
                    vars.put(paramctx.type_().typeName().getText(), normalized);
                    name.setCode(paramctx.type_().typeName().getText());
                    name.setNormalizedCode(normalized);
                    name.setLineOfCode(paramctx.type_().typeName().getStart().getLine());
                    AST.addVertex(name);
                    AST.addEdge(varNode, name);
                }
                int size = ctx.signature().parameters().parameterDecl().size();
                if (ctx.signature().parameters().parameterDecl(size - 1) != null && size > 1) {
                    ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                    varNode.setLineOfCode(ctx.signature().parameters().parameterDecl(size - 1).getStart().getLine());
                    AST.addVertex(varNode);
                    AST.addEdge(parentStack.peek(), varNode);
                    //
                    ASNode type = new ASNode(ASNode.Type.TYPE);
                    type.setCode(ctx.signature().parameters().parameterDecl(size - 1).identifierList().getText());
                    type.setLineOfCode(ctx.signature().parameters().parameterDecl(size - 1).identifierList().getStart().getLine());
                    AST.addVertex(type);
                    AST.addEdge(varNode, type);
                    //
                    ++varsCounter;
                    ASNode name = new ASNode(ASNode.Type.NAME);
                    normalized = "$VARL_" + varsCounter;
                    vars.put(ctx.signature().parameters().parameterDecl(size - 1).type_().getText(), normalized);
                    name.setCode(ctx.signature().parameters().parameterDecl(size - 1).type_().getText());
                    name.setNormalizedCode(normalized);
                    name.setLineOfCode(ctx.signature().parameters().parameterDecl(size - 1).type_().getStart().getLine());
                    AST.addVertex(name);
                    AST.addEdge(varNode, name);
                }
            }
            //
            if (ctx.block() != null) {
                ASNode methodBody = new ASNode(ASNode.Type.BLOCK);
                methodBody.setLineOfCode(ctx.block().getStart().getLine());
                Logger.debug("Adding method block");
                AST.addVertex(methodBody);
                AST.addEdge(parentStack.peek(), methodBody);
                parentStack.push(methodBody);
                visitChildren(ctx.block());
                parentStack.pop();
                resetLocalVars();
            }
            return "";
        }

        @Override
        public String visitVarDecl(GoParser.VarDeclContext ctx) {
            int size = 0;
            int size1 = ctx.varSpec(0).identifierList().IDENTIFIER().size();
            while (size < size1) {
                ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                if (size != size1 - 1)
                    varNode.setLineOfCode(ctx.varSpec(0).identifierList().getStart().getLine());
                else
                    varNode.setLineOfCode(ctx.varSpec(0).identifierList().getStart().getLine());
                AST.addVertex(varNode);
                AST.addEdge(parentStack.peek(), varNode);
                //
                ASNode typeNode = new ASNode(ASNode.Type.TYPE);
                typeNode.setCode(ctx.varSpec(0).type_().getText());
                typeNode.setLineOfCode(ctx.varSpec(0).type_().getStart().getLine());
                AST.addVertex(typeNode);
                AST.addEdge(varNode, typeNode);
                //
                ++varsCounter;
                ASNode nameNode = new ASNode(ASNode.Type.NAME);
                String normalized = "$VARL_" + varsCounter;
                vars.put(ctx.varSpec(0).identifierList().IDENTIFIER(size).getText(), normalized);
                nameNode.setCode(ctx.varSpec(0).identifierList().IDENTIFIER(size).getText());
                nameNode.setNormalizedCode(normalized);
                nameNode.setLineOfCode(ctx.varSpec(0).identifierList().getStart().getLine());
                AST.addVertex(nameNode);
                AST.addEdge(varNode, nameNode);
                //
                //
                if (ctx.varSpec(0).expressionList() != null) {
                    int num = size;
                    if (ctx.varSpec(0).expressionList().expression().size() == 1) {
                        num = 0;
                    }
                    ASNode initNode = new ASNode(ASNode.Type.INIT_VALUE);
                    initNode.setCode("= " + getOriginalCodeText(ctx.varSpec(0).expressionList().expression(num)));
                    initNode.setNormalizedCode("= " + visit(ctx.varSpec(0).expressionList().expression(num)));
                    initNode.setLineOfCode(ctx.varSpec(0).expressionList().expression(num).getStart().getLine());
                    AST.addVertex(initNode);
                    AST.addEdge(varNode, initNode);
                }
                size++;
            }
            return "";
        }

        @Override
        public String visitShortVarDecl(GoParser.ShortVarDeclContext ctx) {
            int size = 0;
            int size1 = ctx.identifierList().IDENTIFIER().size();
            while (size < size1) {
                ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                if (size != size1 - 1)
                    varNode.setLineOfCode(ctx.identifierList().getStart().getLine());
                else
                    varNode.setLineOfCode(ctx.identifierList().getStart().getLine());
                AST.addVertex(varNode);
                AST.addEdge(parentStack.peek(), varNode);
                //
                ASNode typeNode = new ASNode(ASNode.Type.TYPE);
                typeNode.setCode("undetermined");
                typeNode.setLineOfCode(0);
                AST.addVertex(typeNode);
                AST.addEdge(varNode, typeNode);
                //
                ++varsCounter;
                ASNode nameNode = new ASNode(ASNode.Type.NAME);
                String normalized = "$VARL_" + varsCounter;
                vars.put(ctx.identifierList().IDENTIFIER(size).getText(), normalized);
                nameNode.setCode(ctx.identifierList().IDENTIFIER(size).getText());
                nameNode.setNormalizedCode(normalized);
                nameNode.setLineOfCode(ctx.identifierList().getStart().getLine());
                AST.addVertex(nameNode);
                AST.addEdge(varNode, nameNode);
                //
                //
                if (ctx.expressionList() != null) {
                    int num = size;
                    if (ctx.expressionList().expression().size() == 1) {
                        num = 0;
                    }
                    ASNode initNode = new ASNode(ASNode.Type.INIT_VALUE);
                    initNode.setCode("= " + getOriginalCodeText(ctx.expressionList().expression(num)));
                    initNode.setNormalizedCode("= " + visit(ctx.expressionList().expression(num)));
                    initNode.setLineOfCode(ctx.expressionList().expression(num).getStart().getLine());
                    AST.addVertex(initNode);
                    AST.addEdge(varNode, initNode);
                }
                size++;
            }
            return "";
        }

        //=====================================================================//
        //                           STATEMENTS                                //
        //=====================================================================//

        private void visitStatement(ParserRuleContext ctx) {
            Logger.printf(Logger.Level.DEBUG, "Visiting: (%d)  %s", ctx.getStart().getLine(), getOriginalCodeText(ctx));
            ASNode statementNode = new ASNode(ASNode.Type.STATEMENT);
            statementNode.setCode(getOriginalCodeText(ctx));
            statementNode.setLineOfCode(ctx.getStart().getLine());
            Logger.debug("Adding statement " + ctx.getStart().getLine());
            AST.addVertex(statementNode);
            AST.addEdge(parentStack.peek(), statementNode);
        }

        private void visitStatement(ParserRuleContext ctx, String normalized) {
            Logger.printf(Logger.Level.DEBUG, "Visiting: (%d)  %s", ctx.getStart().getLine(), getOriginalCodeText(ctx));
            ASNode statementNode = new ASNode(ASNode.Type.STATEMENT);
            statementNode.setCode(getOriginalCodeText(ctx));
            statementNode.setNormalizedCode(normalized);
            statementNode.setLineOfCode(ctx.getStart().getLine());
            Logger.debug("Adding statement " + ctx.getStart().getLine());
            AST.addVertex(statementNode);
            AST.addEdge(parentStack.peek(), statementNode);
        }

        @Override
        public String visitExpressionStmt(GoParser.ExpressionStmtContext ctx) {
            visitStatement(ctx, visit(ctx.expression()));
            return "";
        }

        @Override
        public String visitBreakStmt(GoParser.BreakStmtContext ctx) {
            if (ctx.IDENTIFIER() == null)
                visitStatement(ctx, null);
            else
                visitStatement(ctx, "break $LABEL");
            return "";
        }

        @Override
        public String visitContinueStmt(GoParser.ContinueStmtContext ctx) {
            if (ctx.IDENTIFIER() == null)
                visitStatement(ctx, null);
            else
                visitStatement(ctx, "continue $LABEL");
            return "";
        }

        @Override
        public String visitGotoStmt(GoParser.GotoStmtContext ctx) {
            if (ctx.IDENTIFIER() == null)
                visitStatement(ctx, null);
            else
                visitStatement(ctx, "goto" + ctx.IDENTIFIER());
            return "";
        }

        @Override
        public String visitDeferStmt(GoParser.DeferStmtContext ctx) {
            if (ctx.expression() == null)
                visitStatement(ctx, null);
            else
                visitStatement(ctx, "defer" + visit(ctx.expression()));
            return "";
        }

        @Override
        public String visitFallthroughStmt(GoParser.FallthroughStmtContext ctx) {
            visitStatement(ctx, null);
            return null;
        }

        @Override
        public String visitReturnStmt(GoParser.ReturnStmtContext ctx) {
            if (ctx.expressionList() == null)
                visitStatement(ctx, null);
            else
                visitStatement(ctx, "return " + visit(ctx.expressionList()));
            return "";
        }

        @Override
        public String visitLabeledStmt(GoParser.LabeledStmtContext ctx) {
            ASNode labelNode = new ASNode(ASNode.Type.LABELED);
            labelNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(labelNode);
            AST.addEdge(parentStack.peek(), labelNode);
            //
            ASNode labelName = new ASNode(ASNode.Type.NAME);
            labelName.setCode(ctx.IDENTIFIER().getText());
            labelName.setNormalizedCode("$LABEL");
            labelName.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(labelName);
            AST.addEdge(labelNode, labelName);
            //
            parentStack.push(labelNode);
            visit(ctx.statement());
            parentStack.pop();
            return "";
        }

      /*  @Override
        public String visitStatement(GoParser.StatementContext ctx)
        {
            if(ctx.declaration()!=null)
                visitStatement(ctx,visit(ctx.declaration()));
            else if(ctx.labeledStmt()!=null)
                visitStatement(ctx,visit(ctx.labeledStmt()));
            else if(ctx.simpleStmt()!=null)
                visitStatement(ctx,visit(ctx.simpleStmt()));
            else if(ctx.selectStmt()!=null)
                visitStatement(ctx,visit(ctx.selectStmt()));
            else if(ctx.switchStmt()!=null)
                visitStatement(ctx,visit(ctx.switchStmt()));
            else if(ctx.goStmt()!=null)
                visitStatement(ctx,visit(ctx.goStmt()));
            else if(ctx.gotoStmt()!=null)
                visitStatement(ctx,visit(ctx.gotoStmt()));
            else if(ctx.returnStmt()!=null)
                visitStatement(ctx,visit(ctx.returnStmt()));
            else if(ctx.breakStmt()!=null)
                visitStatement(ctx,visit(ctx.breakStmt()));
            else if(ctx.fallthroughStmt()!=null)
                visitStatement(ctx,visit(ctx.fallthroughStmt()));
            else if(ctx.forStmt()!=null)
                visitStatement(ctx,visit(ctx.forStmt()));
            else if(ctx.deferStmt()!=null)
                visitStatement(ctx,visit(ctx.deferStmt()));
            else if(ctx.continueStmt()!=null)
                visitStatement(ctx,visit(ctx.continueStmt()));
            else if(ctx.ifStmt()!=null)
                visitStatement(ctx,visit(ctx.labeledStmt()));
            else if(ctx.block()!=null)
                visitStatement(ctx,visit(ctx.block()));
            return null;
        }*/

        @Override
        public String visitIfStmt(GoParser.IfStmtContext ctx) {
// 'if' parExpression statement ('else' statement)?
            ASNode ifNode = new ASNode(ASNode.Type.IF);
            ifNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(ifNode);
            AST.addEdge(parentStack.peek(), ifNode);
            //
            ASNode cond = new ASNode(ASNode.Type.CONDITION);
            cond.setCode(getOriginalCodeText(ctx.expression()));
            cond.setNormalizedCode(ctx.expression().getText());
            cond.setLineOfCode(ctx.expression().getStart().getLine());
            AST.addVertex(cond);
            AST.addEdge(ifNode, cond);
            parentStack.push(cond);
            visit(ctx.expression());
            parentStack.pop();
            //
            ASNode thenNode = new ASNode(ASNode.Type.THEN);
            thenNode.setLineOfCode(ctx.block(0).getStart().getLine());
            AST.addVertex(thenNode);
            AST.addEdge(ifNode, thenNode);
            parentStack.push(thenNode);
            visit(ctx.block(0));
            parentStack.pop();
            //
            if (ctx.block(1) != null) {
                ASNode elseNode = new ASNode(ASNode.Type.ELSE);
                elseNode.setLineOfCode(ctx.block(1).getStart().getLine());
                AST.addVertex(elseNode);
                AST.addEdge(ifNode, elseNode);
                parentStack.push(elseNode);
                visit(ctx.block(1));
                parentStack.pop();
            }
            return "";
        }

        @Override
        public String visitEmptyStmt(GoParser.EmptyStmtContext ctx) {
            //Empty, do nothing.
            return null;
        }

        @Override
        public String visitForStmt(GoParser.ForStmtContext ctx) {
            ASNode forNode;
            if (ctx.forClause() != null)//Common for loop
            {
                // 经典for循环
                forNode = new ASNode(ASNode.Type.FOR);
                forNode.setLineOfCode(ctx.getStart().getLine());
                AST.addVertex(forNode);
                AST.addEdge(parentStack.peek(), forNode);
                // for init
                if (ctx.forClause().initStmt != null) {
                    ASNode forInit = new ASNode(ASNode.Type.FOR_INIT);
                    AST.addVertex(forInit);
                    AST.addEdge(forNode, forInit);
                    if (ctx.forClause().initStmt.assignment() != null) {
                        // 局部变量声明
                        parentStack.push(forInit);
                        visit(ctx.forClause().initStmt.assignment());
                        parentStack.pop();
                    } else {
                        // 表达式(一般为赋值)
                        ASNode expr = new ASNode(ASNode.Type.STATEMENT);
                        expr.setCode("expr");
//                        expr.setCode(getOriginalCodeText(ctx.forControl().forInit().expressionList().expression(0)));
//                        expr.setNormalizedCode(visit(ctx.forControl().forInit().expressionList().expression(0)));
                        expr.setLineOfCode(ctx.forClause().initStmt.getStart().getLine());
                        AST.addVertex(expr);
                        AST.addEdge(forInit, expr);
                        parentStack.push(expr);
                        visit(ctx.forClause().initStmt);
                        parentStack.pop();

                        //
//                        int len = ctx.forControl().forInit().expressionList().expression().size();
//                        for (int i = 1; i < len; ++i) {
//                            expr = new ASNode(ASNode.Type.STATEMENT);
//                            expr.setCode(getOriginalCodeText(ctx.forControl().forInit().expressionList().expression(i)));
//                            expr.setNormalizedCode(visit(ctx.forControl().forInit().expressionList().expression(i)));
//                            expr.setLineOfCode(ctx.forControl().forInit().expressionList().expression(i).getStart().getLine());
//                            AST.addVertex(expr);
//                            AST.addEdge(forInit, expr);
//                        }
                    }
                }
                // for expr
                if (ctx.forClause().expression() != null) {
                    ASNode forExpr = new ASNode(ASNode.Type.CONDITION);
                    forExpr.setCode(getOriginalCodeText(ctx.forClause().expression()));
                    forExpr.setNormalizedCode(visit(ctx.forClause().expression()));
                    forExpr.setLineOfCode(ctx.forClause().expression().getStart().getLine());
                    AST.addVertex(forExpr);
                    AST.addEdge(forNode, forExpr);
                }
                // for update
                if (ctx.forClause().postStmt != null) {
                    ASNode forUpdate = new ASNode(ASNode.Type.FOR_UPDATE);
                    AST.addVertex(forUpdate);
                    AST.addEdge(forNode, forUpdate);
                    //
                    ASNode update = new ASNode(ASNode.Type.STATEMENT);
                    update.setCode(getOriginalCodeText(ctx.forClause().postStmt));
                    update.setNormalizedCode(visit(ctx.forClause().postStmt));
                    update.setLineOfCode(ctx.forClause().postStmt.getStart().getLine());
                    AST.addVertex(update);
                    AST.addEdge(forUpdate, update);
                    //
                    update = new ASNode(ASNode.Type.STATEMENT);
                    update.setCode(getOriginalCodeText(ctx.forClause().postStmt));
                    update.setNormalizedCode(visit(ctx.forClause().postStmt));
                    update.setLineOfCode(ctx.forClause().postStmt.getStart().getLine());
                    AST.addVertex(update);
                    AST.addEdge(forUpdate, update);
                }
            } else//Golang enhanced for loop
            {
                forNode = new ASNode(ASNode.Type.FOR_ENHANCED);
                forNode.setLineOfCode(ctx.getStart().getLine());
                AST.addVertex(forNode);
                AST.addEdge(parentStack.peek(), forNode);
                //
                ASNode varType = new ASNode(ASNode.Type.TYPE);
                varType.setCode("VAR");
                varType.setLineOfCode(ctx.expression().getStart().getLine());
                AST.addVertex(varType);
                AST.addEdge(forNode, varType);
                //
                ++varsCounter;
                ASNode varID = new ASNode(ASNode.Type.NAME);
                String normalized = "$VARL_" + varsCounter;
                vars.put(ctx.expression().expression(0).primaryExpr().getText(), normalized);
                varID.setCode(ctx.expression().expression(0).primaryExpr().getText());
                varID.setNormalizedCode(normalized);
                varID.setLineOfCode(ctx.expression().expression(0).primaryExpr().getStart().getLine());
                AST.addVertex(varID);
                AST.addEdge(forNode, varID);
                //
                ASNode expr = new ASNode(ASNode.Type.IN);
//                expr.setCode(getOriginalCodeText(ctx.forControl().enhancedForControl().expression()));
//                expr.setNormalizedCode(visit(ctx.forControl().enhancedForControl().expression()));
                expr.setCode("expr");
                expr.setLineOfCode(ctx.expression().getStart().getLine());
                AST.addVertex(expr);
                AST.addEdge(forNode, expr);

                parentStack.push(expr);
                visit(ctx.expression());
                parentStack.pop();
            }
            //
            ASNode block = new ASNode(ASNode.Type.BLOCK);
            block.setLineOfCode(ctx.block().getStart().getLine());
            AST.addVertex(block);
            AST.addEdge(forNode, block);
            parentStack.push(block);
            visit(ctx.block());
            parentStack.pop();
            return "";
        }

        @Override
        public String visitSwitchStmt(GoParser.SwitchStmtContext ctx) {
            ASNode switchNode = new ASNode(ASNode.Type.SWITCH);
            switchNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(switchNode);
            AST.addEdge(parentStack.peek(), switchNode);
            //
            ASNode varName = new ASNode(ASNode.Type.NAME);
            varName.setCode(getOriginalCodeText(ctx.exprSwitchStmt().expression()));
            varName.setNormalizedCode(visit(ctx.exprSwitchStmt().expression()));
            varName.setLineOfCode(ctx.exprSwitchStmt().expression().getStart().getLine());
            AST.addVertex(varName);
            AST.addEdge(switchNode, varName);
            //
            if (ctx.exprSwitchStmt().exprCaseClause() != null) {
                for (GoParser.ExprCaseClauseContext grpx : ctx.exprSwitchStmt().exprCaseClause()) {
                    ASNode blockNode = new ASNode(ASNode.Type.BLOCK);
                    blockNode.setLineOfCode(grpx.statementList(0).getStart().getLine());
                    AST.addVertex(blockNode);
                    for (GoParser.ExprSwitchCaseContext lblctx : grpx.exprSwitchCase())
                        visitSwitchLabel(lblctx, switchNode, blockNode);
                    parentStack.push(blockNode);
                    for (GoParser.StatementListContext blk : grpx.statementList())
                        visit(blk);
                    parentStack.pop();
                }
            }
            if (ctx.exprSwitchStmt().exprSwitchCase() != null && ctx.exprSwitchStmt().exprSwitchCase().size() > 0) {
                ASNode blockNode = new ASNode(ASNode.Type.BLOCK);
                blockNode.setLineOfCode(ctx.exprSwitchStmt().exprCaseClause(0).exprSwitchCase(0).getStart().getLine());
                AST.addVertex(blockNode);
                for (GoParser.ExprSwitchCaseContext lblctx : ctx.exprSwitchCaseStmt())
                    visitSwitchLabel(lblctx, switchNode, blockNode);
            }
            return "";
        }

        private void visitSwitchLabel(GoParser.ExprSwitchCaseContext lblctx, ASNode switchNode, ASNode blockNode) {
            ASNode caseNode;
            if (lblctx.expressionList() != null) {
                caseNode = new ASNode(ASNode.Type.CASE);
                caseNode.setCode(lblctx.expressionList().getText());
                caseNode.setLineOfCode(lblctx.getStart().getLine());
            } else {
                caseNode = new ASNode(ASNode.Type.DEFAULT);
                caseNode.setLineOfCode(lblctx.getStart().getLine());
            }
            AST.addVertex(caseNode);
            AST.addEdge(switchNode, caseNode);
            AST.addEdge(caseNode, blockNode);
        }

        @Override
        public String visitSelectStmt(GoParser.SelectStmtContext ctx) {
            ASNode selectNode = new ASNode(ASNode.Type.SWITCH);
            selectNode.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(selectNode);
            AST.addEdge(parentStack.peek(), selectNode);
            //
            ASNode varName = new ASNode(ASNode.Type.NAME);
            varName.setCode("select");
            varName.setNormalizedCode("select");
            varName.setLineOfCode(ctx.getStart().getLine());
            AST.addVertex(varName);
            AST.addEdge(selectNode, varName);
            //
            if (ctx.commClause() != null) {
                for (GoParser.CommClauseContext grpx : ctx.commClause()) {
                    ASNode blockNode = new ASNode(ASNode.Type.BLOCK);
                    if (grpx.statementList() != null)
                        blockNode.setLineOfCode(grpx.statementList().getStart().getLine());
                    else
                        blockNode.setLineOfCode(grpx.getStart().getLine() - 1);
                    AST.addVertex(blockNode);
                    for (GoParser.CommCaseContext lblctx : grpx.commCase(0))
                        visitselectLabel(lblctx, selectNode, blockNode);
                    parentStack.push(blockNode);
                    for (GoParser.StatementListContext blk : grpx.statementList(0))
                        visit(blk);
                    parentStack.pop();
                }
            }
            if (ctx.commClause() != null && ctx.commClause().size() > 0) {
                ASNode blockNode = new ASNode(ASNode.Type.BLOCK);
                blockNode.setLineOfCode(ctx.commClause(0).getStart().getLine());
                AST.addVertex(blockNode);
                for (GoParser.CommCaseContext lblctx : ctx.commCase())
                    visitselectLabel(lblctx, selectNode, blockNode);
            }
            return "";
        }

        private void visitselectLabel(GoParser.CommCaseContext lblctx, ASNode selectNode, ASNode blockNode) {
            ASNode caseNode;
            if (lblctx.recvStmt() != null) {
                caseNode = new ASNode(ASNode.Type.CASE);
                caseNode.setCode(lblctx.recvStmt().getText());
                caseNode.setLineOfCode(lblctx.getStart().getLine());
            } else if (lblctx.sendStmt() != null) {
                caseNode = new ASNode(ASNode.Type.CASE);
                caseNode.setCode(lblctx.sendStmt().getText());
                caseNode.setLineOfCode(lblctx.getStart().getLine());
            } else {
                caseNode = new ASNode(ASNode.Type.DEFAULT);
                caseNode.setLineOfCode(lblctx.getStart().getLine());
            }
            AST.addVertex(caseNode);
            AST.addEdge(selectNode, caseNode);
            AST.addEdge(caseNode, blockNode);
        }


        //=====================================================================//
        //                          EXPRESSIONS                                //
        //=====================================================================//

        @Override
        public String visitPrimaryExpr(GoParser.PrimaryExprContext ctx) {
            if (ctx.operand() != null && ctx.operand().expression() != null)
                return "(" + visit(ctx.operand().expression()) + ")";
            else if (ctx.conversion() != null && ctx.conversion().expression() != null)
                return "(" + visit(ctx.conversion().expression()) + ")";
            else if (ctx.methodExpr() != null)
                return "(" + visit(ctx.methodExpr()) + ")";
            else if (ctx.primaryExpr() != null) {
                if (ctx.arguments() != null) {
                    ASNode invokeNode = new ASNode(ASNode.Type.FUNC_INVOKE);
                    invokeNode.setLineOfCode(ctx.getStart().getLine());
                    invokeNode.setCode("");
                    AST.addVertex(invokeNode);
                    AST.addEdge(parentStack.peek(), invokeNode);
                    parentStack.push(invokeNode);

                    ASNode methodNode = new ASNode(ASNode.Type.FUNC_NAME);
                    methodNode.setLineOfCode(ctx.primaryExpr().getStart().getLine());
                    AST.addVertex(methodNode);
                    AST.addEdge(parentStack.peek(), methodNode);

                    if (ctx.primaryExpr().getChildCount() == 1) {
                        methodNode.setCode(getOriginalCodeText(ctx.primaryExpr()));
                    } else {
                        methodNode.setCode("");
                        parentStack.push(methodNode);
                        visit(ctx.primaryExpr());
                        parentStack.pop();
                    }

                    if (ctx.arguments().expressionList() != null) {
                        ASNode params = new ASNode(ASNode.Type.PARAMS);
                        params.setLineOfCode(ctx.arguments().expressionList().getStart().getLine());
                        AST.addVertex(params);
                        AST.addEdge(parentStack.peek(), params);
                        parentStack.push(params);

                        for (GoParser.ExpressionContext expressionContext : ctx.arguments().expressionList().expression()) {
                            if (expressionContext.getChildCount() == 1) {
                                ASNode param = new ASNode(ASNode.Type.PARAMETERARG);
                                param.setLineOfCode(expressionContext.getStart().getLine());
                                param.setCode(getOriginalCodeText(expressionContext));
                                AST.addVertex(param);
                                AST.addEdge(parentStack.peek(), param);
                            } else {
                                visit(expressionContext);
                            }
                        }
                        parentStack.pop();
                    }

                    parentStack.pop();
                    return null;

                } else if (ctx.DOT() != null && ctx.IDENTIFIER() != null) {
                    ASNode callExpr = new ASNode(ASNode.Type.MEMBER_ACCESS);
                    callExpr.setLineOfCode(ctx.getStart().getLine());
                    callExpr.setCode(".");
                    AST.addVertex(callExpr);
                    AST.addEdge(parentStack.peek(), callExpr);
                    parentStack.push(callExpr);

                    ASNode scopeNode = new ASNode(ASNode.Type.SCOPE);
                    scopeNode.setLineOfCode(ctx.primaryExpr().getStart().getLine());
                    AST.addVertex(scopeNode);
                    AST.addEdge(callExpr, scopeNode);

                    if (ctx.primaryExpr().getChildCount() == 1) {
                        scopeNode.setCode(getOriginalCodeText(ctx.primaryExpr()));
                        AST.addEdge(parentStack.peek(), scopeNode);
                    } else {
                        scopeNode.setCode("");
                        parentStack.push(scopeNode);
                        visit(ctx.primaryExpr());
                        parentStack.pop();
                    }
                    //
                    ASNode nameNode = new ASNode(ASNode.Type.NAME);
                    nameNode.setLineOfCode(0);
                    nameNode.setCode(ctx.IDENTIFIER().getText());
                    AST.addVertex(nameNode);
                    AST.addEdge(parentStack.peek(), nameNode);
                    //
                    parentStack.pop();
                    return null;
                } else if (ctx.index() != null) {
                    return visit(ctx.primaryExpr()) + visit(ctx.index().expression());
                } else if (ctx.typeAssertion() != null) {
                    return visit(ctx.primaryExpr()) + " " + visit(ctx.typeAssertion());
                }
            }
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitExpression(GoParser.ExpressionContext ctx) {
            if (ctx.primaryExpr() != null)
                return visit(ctx.primaryExpr());
            else if (ctx.unary_op != null) {
                String op = " ";
                op = ctx.unary_op.getText();
                ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
                opNode.setLineOfCode(0);
                opNode.setCode(op + "");
                AST.addVertex(opNode);
                AST.addEdge(parentStack.peek(), opNode);

                GoParser.ExpressionContext expressionContext = ctx.expression(0);

                if (expressionContext.getChildCount() == 1) {
                    GoParser.PrimaryExprContext primary = expressionContext.primaryExpr();
                    // token
                    String token = primary.getText();
                    ASNode tokenNode;
                    tokenNode = primary.IDENTIFIER() != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(primary.getStart().getLine());
                    tokenNode.setCode(token);
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(expressionContext);
                }
                parentStack.pop();

                return null;
            } else if (ctx.mul_op != null) {
                String op = ctx.mul_op.getText();
                ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
                opNode.setCode(op + "");
                AST.addVertex(opNode);
                AST.addEdge(parentStack.peek(), opNode);
                parentStack.push(opNode);

                for (GoParser.ExpressionContext expressionContext : ctx.expression()) {
                    if (expressionContext.getChildCount() == 1) {
                        GoParser.PrimaryExprContext primary = expressionContext.primaryExpr();
                        // token
                        String token = primary.getText();
                        ASNode tokenNode;
                        tokenNode = primary.IDENTIFIER() != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                        tokenNode.setLineOfCode(primary.getStart().getLine());
                        tokenNode.setCode(token);
                        AST.addVertex(tokenNode);
                        AST.addEdge(parentStack.peek(), tokenNode);
                    } else {
                        // expression
                        visit(expressionContext);
                    }
                }
                parentStack.pop();
                return null;
            } else if (ctx.add_op != null) {
                String op = ctx.add_op.getText();
                ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
                opNode.setLineOfCode(0);
                opNode.setCode(op + "");
                AST.addVertex(opNode);
                AST.addEdge(parentStack.peek(), opNode);
                parentStack.push(opNode);

                for (GoParser.ExpressionContext expressionContext : ctx.expression()) {
                    if (expressionContext.getChildCount() == 1) {
                        GoParser.PrimaryExprContext primary = expressionContext.primaryExpr();
                        // token
                        String token = primary.getText();
                        ASNode tokenNode;
                        tokenNode = primary.IDENTIFIER() != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                        tokenNode.setLineOfCode(primary.getStart().getLine());
                        tokenNode.setCode(token);
                        AST.addVertex(tokenNode);
                        AST.addEdge(parentStack.peek(), tokenNode);
                    } else {
                        // expression
                        visit(expressionContext);
                    }
                }
                parentStack.pop();
                return null;
            } else if (ctx.rel_op != null) {
                String op = ctx.rel_op.getText();
                ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
                opNode.setLineOfCode(0);
                opNode.setCode(op + "");
                AST.addVertex(opNode);
                AST.addEdge(parentStack.peek(), opNode);
                parentStack.push(opNode);

                for (GoParser.ExpressionContext expressionContext : ctx.expression()) {
                    if (expressionContext.getChildCount() == 1) {
                        GoParser.PrimaryExprContext primary = expressionContext.primaryExpr();
                        // token
                        String token = primary.getText();
                        ASNode tokenNode;
                        tokenNode = primary.IDENTIFIER() != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                        tokenNode.setLineOfCode(primary.getStart().getLine());
                        tokenNode.setCode(token);
                        AST.addVertex(tokenNode);
                        AST.addEdge(parentStack.peek(), tokenNode);
                    } else {
                        // expression
                        visit(expressionContext);
                    }
                }
                parentStack.pop();
                return null;
            } else if (ctx.LOGICAL_AND() != null) {
                ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
                opNode.setLineOfCode(0);
                opNode.setCode("&&");
                AST.addVertex(opNode);
                AST.addEdge(parentStack.peek(), opNode);
                parentStack.push(opNode);

                for (GoParser.ExpressionContext expressionContext : ctx.expression()) {
                    if (expressionContext.getChildCount() == 1) {
                        GoParser.PrimaryExprContext primary = expressionContext.primaryExpr();
                        // token
                        String token = primary.getText();
                        ASNode tokenNode;
                        tokenNode = primary.IDENTIFIER() != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                        tokenNode.setLineOfCode(primary.getStart().getLine());
                        tokenNode.setCode(token);
                        AST.addVertex(tokenNode);
                        AST.addEdge(parentStack.peek(), tokenNode);
                    } else {
                        // expression
                        visit(expressionContext);
                    }
                }
                parentStack.pop();
                return null;
            } else if (ctx.LOGICAL_OR() != null) {
                ASNode opNode = new ASNode(ASNode.Type.OPERATOR);
                opNode.setLineOfCode(0);
                opNode.setCode("||");
                AST.addVertex(opNode);
                AST.addEdge(parentStack.peek(), opNode);
                parentStack.push(opNode);

                for (GoParser.ExpressionContext expressionContext : ctx.expression()) {
                    if (expressionContext.getChildCount() == 1) {
                        GoParser.PrimaryExprContext primary = expressionContext.primaryExpr();
                        // token
                        String token = primary.getText();
                        ASNode tokenNode;
                        tokenNode = primary.IDENTIFIER() != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                        tokenNode.setLineOfCode(primary.getStart().getLine());
                        tokenNode.setCode(token);
                        AST.addVertex(tokenNode);
                        AST.addEdge(parentStack.peek(), tokenNode);
                    } else {
                        // expression
                        visit(expressionContext);
                    }
                }
                parentStack.pop();
                return null;
            }
            return null;
        }

        @Override
        public String visitIncDecStmt(GoParser.IncDecStmtContext ctx) {
            ASNode opNode = new ASNode(ASNode.Type.POSTFIX_UNARY_OPERATION);
            String op = ctx.getText().substring(ctx.expression().getText().length());
            opNode.setLineOfCode(ctx.getStart().getLine());
            opNode.setCode(op);
            AST.addVertex(opNode);
            AST.addEdge(parentStack.peek(), opNode);
            parentStack.push(opNode);

            if (ctx.expression().getChildCount() == 1) {
                ASNode varNode = new ASNode(ASNode.Type.VARIABLE);
                varNode.setLineOfCode(0);
                varNode.setCode(getOriginalCodeText(ctx.expression()));
                AST.addVertex(varNode);
                AST.addEdge(parentStack.peek(), varNode);
            } else {
                visit(ctx.expression());
            }

            parentStack.pop();

            return null;
        }


        @Override
        public String visitArguments(GoParser.ArgumentsContext ctx) {
            if (ctx.expressionList() == null)
                return "()";
            return "(" + visit(ctx.expressionList()) + ")";
        }

        @Override
        public String visitAssignment(GoParser.AssignmentContext ctx) {
            ASNode assignNode = new ASNode(ASNode.Type.ASSIGNMENT);
            assignNode.setLineOfCode(ctx.getStart().getLine());
            assignNode.setCode(ctx.assign_op().getText());
            AST.addVertex(assignNode);
            AST.addEdge(parentStack.peek(), assignNode);
            parentStack.push(assignNode);
            for (GoParser.ExpressionListContext expressionContext : ctx.expressionList()) {
                if (expressionContext.expression(0).getChildCount() == 1) {
                    GoParser.PrimaryExprContext primary = expressionContext.expression(0).primaryExpr();
                    // token
                    String token = primary.getText();
                    ASNode tokenNode;
                    tokenNode = primary.IDENTIFIER() != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(primary.getStart().getLine());
                    tokenNode.setCode(token);
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(expressionContext.expression(0));
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitSendStmt(GoParser.SendStmtContext ctx) {
            ASNode assignNode = new ASNode(ASNode.Type.ASSIGNMENT);
            assignNode.setLineOfCode(ctx.getStart().getLine());
            assignNode.setCode(ctx.RECEIVE().getText());
            AST.addVertex(assignNode);
            AST.addEdge(parentStack.peek(), assignNode);
            parentStack.push(assignNode);
            for (GoParser.ExpressionContext expressionContext : ctx.expression()) {
                if (expressionContext.getChildCount() == 1) {
                    GoParser.PrimaryExprContext primary = expressionContext.primaryExpr();
                    // token
                    String token = primary.getText();
                    ASNode tokenNode;
                    tokenNode = primary.IDENTIFIER() != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                    tokenNode.setLineOfCode(primary.getStart().getLine());
                    tokenNode.setCode(token);
                    AST.addVertex(tokenNode);
                    AST.addEdge(parentStack.peek(), tokenNode);
                } else {
                    // expression
                    visit(expressionContext);
                }
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitRecvStmt(GoParser.RecvStmtContext ctx) {
            ASNode assignNode = new ASNode(ASNode.Type.ASSIGNMENT);
            assignNode.setLineOfCode(ctx.getStart().getLine());
            assignNode.setCode(ctx.ASSIGN().getText());
            AST.addVertex(assignNode);
            AST.addEdge(parentStack.peek(), assignNode);
            parentStack.push(assignNode);
            GoParser.ExpressionContext expressionContext = ctx.expression();
            if (expressionContext.getChildCount() == 1) {
                GoParser.PrimaryExprContext primary = expressionContext.primaryExpr();
                // token
                String token = primary.getText();
                ASNode tokenNode;
                tokenNode = primary.IDENTIFIER() != null ? new ASNode(ASNode.Type.VARIABLE) : new ASNode(ASNode.Type.CONST);
                tokenNode.setLineOfCode(primary.getStart().getLine());
                tokenNode.setCode(token);
                AST.addVertex(tokenNode);
                AST.addEdge(parentStack.peek(), tokenNode);
            } else {
                // expression
                visit(expressionContext);
            }
            parentStack.pop();
            return null;
        }

        @Override
        public String visitExpressionList(GoParser.ExpressionListContext ctx) {
            for (GoParser.ExpressionContext expressionContext : ctx.expression()) {
                visit(expressionContext);
            }
            return null;
        }


        //=====================================================================//
        //                          PRIVATE METHODS                            //
        //=====================================================================//


        /**
         * Get the original program text for the given parser-rule context.
         * This is required for preserving white-spaces.
         */
        private String getOriginalCodeText(ParserRuleContext ctx) {
            if (ctx == null)
                return "";
            int start = ctx.start.getStartIndex();
            int stop = ctx.stop.getStopIndex();
            Interval interval = new Interval(start, stop);
            return ctx.start.getInputStream().getText(interval);
        }

        private void resetLocalVars() {
            vars.clear();
            varsCounter = 0;
        }

        private String normalizedIdentifier(TerminalNode id) {
            String normalized = vars.get(id.getText());
            if (normalized == null || normalized.isEmpty())
                normalized = fields.get(id.getText());
            if (normalized == null || normalized.isEmpty())
                normalized = methods.get(id.getText());
            if (normalized == null || normalized.isEmpty())
                normalized = id.getText();
            return normalized;
        }

    }

}
